/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


import { ts, ArkMethod, ArkFile, ArkAssignStmt, Type, FieldSignature } from "arkanalyzer/lib";
import Logger, { LOG_MODULE_TYPE } from 'arkanalyzer/lib/utils/logger';
import { BaseChecker, BaseMetaData } from "../BaseChecker";
import { Defects, IssueReport } from '../../model/Defects';
import { ClassMatcher, FileMatcher, MatcherCallback, MatcherTypes, MethodMatcher } from "../../matcher/Matchers";
import { Rule } from "../../model/Rule";
import { RuleListUtil } from "../../utils/common/DefectsList";
import { RuleFix } from '../../model/Fix';
import { Utils } from '../../Index';

interface lineColumnInfo {
    line: number,
    character: number
}
const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'NoDynamicDeleteCheck');
const gmetaData: BaseMetaData = {
    severity: 2,
    ruleDocPath: "docs/no-dynamic-delete-check.md", // TODO: support url
    description: "Do not delete dynamically computed property keys."
};
export class NoDynamicDeleteCheck implements BaseChecker {
    readonly metaData: BaseMetaData = gmetaData;
    public rule: Rule;
    public defects: Defects[] = [];
    public issues: IssueReport[] = [];
    private fileMatcher: FileMatcher = {
        matcherType: MatcherTypes.FILE,
    };
    private classMatcher: ClassMatcher = {
        file: [this.fileMatcher],
        matcherType: MatcherTypes.CLASS
    };
    private methodMatcher: MethodMatcher = {
        matcherType: MatcherTypes.METHOD,
        class: [this.classMatcher]
    };
    public registerMatchers(): MatcherCallback[] {
        const matchBuildCb: MatcherCallback = {
            matcher: this.fileMatcher,
            callback: this.check
        }
        return [matchBuildCb];
    }
    private issueMap: Map<string, IssueReport> = new Map();
    public check = (targetField: ArkFile) => {
        if (this.getFileExtension(targetField.getName()) !== '.ets') {
            const code = targetField.getCode();
            if (!code) {
                return;
            }
            const variableNameArray: string[] = this.checkVariableName(code);
            const deleteExpressions: string[] = this.findDeleteExpressions(code);
            if (variableNameArray.length > 0 && deleteExpressions.length > 0) {
                for (const deleteExpression of deleteExpressions) {
                    for (const variableName of variableNameArray) {
                        if (deleteExpression.includes(variableName)) {
                            const deleteProperties: string[] = this.extractPropertyKeysFromDeleteExpressions(deleteExpression);
                            if (deleteProperties.length > 0) {
                                for (const deleteProperty of deleteProperties) {
                                    this.getDeletePropertyPosition(targetField, deleteExpression, deleteProperty) ?? [];
                                    this.reportSortedIssues();
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    private getFileExtension(filePath: string): string {
        const lastDotIndex = filePath.lastIndexOf('.');
        if (lastDotIndex === -1) {
            return '';
        }
        return filePath.substring(lastDotIndex);
    }
    public checkVariableName(code: string): string[] {
        const sourceFile = ts.createSourceFile('temp.ts', code, ts.ScriptTarget.Latest, true);

        const variableNames: string[] = [];

        function traverse(node: ts.Node) {
            if (ts.isVariableStatement(node)) {
                for (const declaration of node.declarationList.declarations) {
                    if (declaration.name.getText().trim() !== '') {
                        variableNames.push(declaration.name.getText());
                    }
                }
            }
            // 递归遍历所有子节点
            ts.forEachChild(node, childNode => traverse(childNode));
        }

        // 从源文件开始遍历
        traverse(sourceFile);

        // 返回找到的变量名称数组
        return variableNames;
    }
    private findDeleteExpressions(code: string): string[] {
        const sourceFile = ts.createSourceFile('temp.ts', code, ts.ScriptTarget.Latest, true);
        const deleteExpressions: string[] = [];

        function traverse(node: ts.Node): void {
            if (ts.isDeleteExpression(node)) {
                const text = node.getText();
                if (text) {
                    deleteExpressions.push(text);
                }
            }
            ts.forEachChild(node, childNode => traverse(childNode));
        }

        traverse(sourceFile);
        return deleteExpressions;
    }
    public checkIsHasVariableName(code: string, variableToFind: string): boolean {
        const sourceFile = ts.createSourceFile('temp.ts', code, ts.ScriptTarget.Latest, true);
        let found = false;

        function traverse(node: ts.Node) {
            if (ts.isVariableStatement(node)) {
                for (const declaration of node.declarationList.declarations) {
                    if (declaration.name.getText().trim() === variableToFind) {
                        found = true;
                        return;
                    }
                }
            }
            ts.forEachChild(node, childNode => {
                if (!found) {
                    traverse(childNode);
                }
            });
        }

        // 从源文件开始遍历
        traverse(sourceFile);

        // 返回是否找到变量名的结果
        return found;
    }
    private extractPropertyKeysFromDeleteExpressions(code: string): string[] {
        const sourceFile = ts.createSourceFile('temp.ts', code, ts.ScriptTarget.Latest, true);
        const propertyKeys: string[] = [];

        function isAlphabetic(str: string): boolean {
            for (let i = 0; i < str.length; i++) {
                const code = str.charCodeAt(i);
                if ((code < 65 || code > 90) && (code < 97 || code > 122)) {
                    return false;
                }
            }
            return true;
        }
        function visit(node: ts.Node): void {
            if (ts.isDeleteExpression(node)) {
                const expression = node.expression;
                if (ts.isElementAccessExpression(expression)) {
                    const argumentExpression = expression.argumentExpression;
                    if (ts.isStringLiteral(argumentExpression)) {
                        const keyText = argumentExpression.text;
                        if (isAlphabetic(keyText) && !keyText.includes('+') && !keyText.includes('-')) {
                            propertyKeys.push(keyText);
                        }
                    }
                    else if (ts.isIdentifier(argumentExpression)) {
                        const identifierText = argumentExpression.getText();
                        if (isAlphabetic(identifierText)) {
                            propertyKeys.push(identifierText);
                        }
                    }
                    else if (ts.isPropertyAccessExpression(argumentExpression)) {
                        const keyText = argumentExpression.getText();
                        propertyKeys.push(keyText);
                    }
                    else if (ts.isCallExpression(argumentExpression)) {
                        const keyText = argumentExpression.getText();
                        propertyKeys.push(keyText);
                    } else if (ts.isBinaryExpression(argumentExpression)) {
                        const keyText = argumentExpression.getText();
                        propertyKeys.push(keyText);
                    } else {
                        const keyText = argumentExpression.getText();
                        if (keyText.startsWith('+') || keyText.startsWith('-')) {
                            const stringKeyText = String(keyText);
                            const target = stringKeyText.substring(1);
                            if (isAlphabetic(target)) {
                                propertyKeys.push(keyText);
                            }
                        }
                    }
                }
            }
            ts.forEachChild(node, visit);
        }

        visit(sourceFile);
        return propertyKeys;
    }
    private getDeletePropertyPosition(targetFile: ArkFile, deleteExpressionText: string, propertyName: string): { line: number, character: number }[] | null {
        const code = targetFile.getCode();
        const sourceFile = ts.createSourceFile('temp.ts', code, ts.ScriptTarget.Latest, true);

        const visit = (node: ts.Node): { line: number, character: number } | null => {
            if (ts.isDeleteExpression(node)) {
                const nodeText = node.getText();
                if (nodeText === deleteExpressionText) {
                    const expression = node.expression;
                    if (ts.isElementAccessExpression(expression)) {
                        const argumentExpression = expression.argumentExpression;
                        const argText = argumentExpression.getText();
                        let pos;
                        let end;
                        let fixText;
                        const index = propertyName.indexOf(argText);
                        if (index == 0) {
                            pos = argumentExpression.getStart();
                            end = argumentExpression.getEnd();
                            fixText = propertyName;
                        } else {
                            if (ts.isBinaryExpression(argumentExpression)) {
                                pos = argumentExpression.getStart();
                                end = argumentExpression.getEnd();
                                fixText = propertyName;
                            } else {
                                pos = argumentExpression.getStart() - 1;
                                end = argumentExpression.getEnd() + 1;
                                fixText = `.${propertyName}`;
                            }
                        }
                        let propertyNamePosition;
                        if (ts.isStringLiteral(argumentExpression)) {
                            propertyNamePosition = argText.indexOf(propertyName);
                        }
                        else {
                            propertyNamePosition = argText.indexOf(propertyName) + 1;
                        }
                        if (propertyNamePosition !== -1) {
                            // 获取 propertyName 在 sourceFile 中的起始位置
                            const propertyNameStart = argumentExpression.getStart() + propertyNamePosition;

                            const positionInfo = ts.getLineAndCharacterOfPosition(sourceFile, propertyNameStart);
                            const defect = this.addIssueReport(targetFile, { line: positionInfo.line + 1, character: positionInfo.character });
                            let fix: RuleFix = this.ruleFix(pos, end, fixText);
                            if (defect) {
                                this.issueMap.set(defect.fixKey, { defect, fix });
                            }
                            // 修改行号，使其加1
                            return { line: positionInfo.line + 1, character: positionInfo.character };
                        }
                    }
                }
            }
            return null;
        }

        let foundPositions: { line: number, character: number }[] = [];
        const visitor: (node: ts.Node) => void = (node) => {
            const position = visit(node);
            if (position) {
                foundPositions.push(position);
            }
            ts.forEachChild(node, visitor);
        };

        visitor(sourceFile);

        // 如果找到了匹配的位置，返回它们；否则返回 null
        return foundPositions.length > 0 ? foundPositions : null;
    }
    private ruleFix(pos: number, end: number, fixText: string): RuleFix {
        return { range: [pos, end], text: fixText };
    }
    private reportSortedIssues(): void {
        if (this.issueMap.size === 0) {
            return;
        }

        const sortedIssues = Array.from(this.issueMap.entries())
            .sort(([keyA], [keyB]) => Utils.sortByLineAndColumn(keyA, keyB));

        this.issues = [];

        sortedIssues.forEach(([_, issue]) => {
            RuleListUtil.push(issue.defect);
            this.issues.push(issue);
        });
    }
    private addIssueReport(arkFile: ArkFile, lineAndColumn: lineColumnInfo) {
        const severity = this.rule.alert ?? this.metaData.severity;
        const warnInfo = this.getLineAndColumn(arkFile, lineAndColumn);
        this.metaData.description = this.metaData.description.padEnd(130);
        if (warnInfo) {
            const fixKey = warnInfo.line + '%' + warnInfo.startCol + '%' + this.rule.ruleId;
            const mergeKey = warnInfo.filePath + '%' + fixKey + '%' + this.metaData.description + this.rule.ruleId;
            const defect = new Defects(warnInfo.line, warnInfo.startCol, this.metaData.description, severity, this.rule.ruleId,
                mergeKey, this.metaData.ruleDocPath, true, false, false, fixKey);
            this.defects.push(defect);

            return defect;
        }
    }
    private getLineAndColumn(arkfile: ArkFile, lineColumn: lineColumnInfo) {
        if (arkfile) {
            const originPath = arkfile.getFilePath();
            return { line: lineColumn.line, startCol: lineColumn.character, filePath: originPath };
        } else {
            logger.debug('arkFile is null');
        }
        return null;
    }
}